Skip to content

chore(training): correct f-strings; log param counts via utils - #113

Merged
d-ulker merged 30 commits into
mainfrom
split/labels-training
Aug 24, 2025
Merged

chore(training): correct f-strings; log param counts via utils#113
d-ulker merged 30 commits into
mainfrom
split/labels-training

Conversation

@d-ulker

@d-ulker d-ulker commented Aug 23, 2025

Copy link
Copy Markdown
Owner

Summary:

Fixed literal brace logging -> proper f-strings
Switched parameter logging to src/utils.count_model_params
File: src/models/emotion_detection/training_pipeline.py
Behavior unchanged; clearer, accurate logs

Summary by Sourcery

Improve training pipeline logging by converting all logger messages to f-strings for accurate interpolation and leverage the count_model_params utility for parameter count logging

Enhancements:

  • Convert logger calls from literal braces to f-strings across the training pipeline for clearer, correct log output
  • Use utils.count_model_params to log trainable parameter counts instead of the model’s internal count method

Summary by CodeRabbit

  • New Features

    • Shows trainable parameter count on model init.
    • Emotion trainer exposes datasets and dataloaders for inspection.
    • New utility to count model parameters.
    • Enhanced security headers with finer configuration and richer user‑agent analysis.
    • Loader cleans up temporary extraction dirs when no model is found.
  • Behavior Changes

    • Training API now defaults to production mode; dev_mode uses smaller datasets and larger batches.
    • Token blacklist stores expirations and auto-cleans expired entries.
  • Refactor

    • Consolidated, parameterized logging and gated debug diagnostics.

@d-ulker d-ulker self-assigned this Aug 23, 2025
Copilot AI review requested due to automatic review settings August 23, 2025 15:41
@sourcery-ai

sourcery-ai Bot commented Aug 23, 2025

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactored extensive logger statements to use f-strings for proper variable interpolation and leveraged the shared count_model_params utility for reporting trainable parameter counts, enhancing log clarity without altering runtime behavior.

File-Level Changes

Change Details Files
Convert all logger calls from literal-brace strings to f-strings
  • Replaced device, batch size, dataset size, loss function, class weights, checkpoint path, unfreezing, validation frequency, data distribution, model output, predictions, loss analysis, class-loss and gradient-norm logs to f-strings
  • Removed outdated extra{"format_args":True} in progressive unfreezing log
src/models/emotion_detection/training_pipeline.py
Use shared utility for parameter counting
  • Replaced direct model.count_parameters() invocation with count_model_params(self.model, only_trainable=True)
src/models/emotion_detection/training_pipeline.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Aug 23, 2025

Copy link
Copy Markdown

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Warning

Rate limit exceeded

@uelkerd has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 18 minutes and 58 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 0ea88e9 and 50354ed.

📒 Files selected for processing (2)
  • src/models/emotion_detection/training_pipeline.py (20 hunks)
  • src/security_headers.py (12 hunks)

Walkthrough

Refactors the emotion-detection training pipeline (dev-mode dataset scaling, helper-driven training flow, removed debug_mode); HF loader cleans unused extracted archives; JWT blacklist stores expirations and prunes expired tokens; security headers middleware expands configuration and enhances UA analysis; adds a model-parameter counting util.

Changes

Cohort / File(s) Summary
Training pipeline core
src/models/emotion_detection/training_pipeline.py
Removed debug_mode param and changed train_emotion_detection_model default dev_mode to False; added public attributes train_dataset, val_dataset, test_dataset and train_dataloader, val_dataloader, test_dataloader; dev_mode downsamples (~5% train / ~10% val) and increases batch_size (capped at min(128, batch_size*8)); refactored training loop into helpers (_handle_progressive_unfreezing, _train_single_batch, _maybe_validate_and_early_stop, logging helpers); numeric/parameterized logging; uses count_model_params for param count; standardized checkpoint logging.
HF model loader
src/models/emotion_detection/hf_loader.py
Formatting and readability edits; intensity calc refactored to if/elif/else; when loading from archive_url, removes temporary extraction dir if no model found; minor cache/archive path and doc/import reformatting; public API unchanged.
JWT manager
src/security/jwt_manager.py
Added module logger; blacklist storage changed to dict[token] = exp_datetime; blacklist_token decodes exp and records expiration; cleanup_expired_tokens prunes expired entries; exception logging formatting adjusted; payload formatting minor tweaks; signatures unchanged.
Security headers middleware
src/security_headers.py
Reorganized imports; CSP now read from YAML config_path with fallback; request_id is a composite hashed string and correlation_id defaults to it; header injection standardized; SecurityHeadersConfig expanded with multiple boolean flags and UA-related fields; added enhanced UA analysis and suspicious-combination detection; response security logging extended.
Utilities
src/utils.py
New utility count_model_params(model: torch.nn.Module, only_trainable: bool = False) -> int to return total or only trainable parameter count.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant Trainer
  participant DataPrep
  participant Model
  participant Loss
  participant Validator

  User->>Trainer: train_emotion_detection_model(dev_mode)
  Trainer->>DataPrep: prepare_data(dev_mode)  -- downsample & adjust batch size if dev
  DataPrep-->>Trainer: datasets & dataloaders
  loop per epoch
    Trainer->>Trainer: _handle_progressive_unfreezing(epoch)
    loop per batch
      Trainer->>Model: forward(batch)
      Model-->>Trainer: logits
      Trainer->>Loss: compute(logits, labels)
      Loss-->>Trainer: loss
      Trainer->>Trainer: _train_single_batch(...) and _log_progress(...)
      alt first batch (detailed logs)
        Trainer->>Trainer: _log_data_distribution/_log_model_output/_log_loss_analysis
      end
      Trainer->>Trainer: _log_gradient_stats_before/_log_gradient_stats_after
    end
    Trainer->>Trainer: _maybe_validate_and_early_stop()
    alt validation runs
      Trainer->>Validator: evaluate(model, val_loader)
      Validator-->>Trainer: metrics
      Trainer->>Trainer: save/checkpoint if best
    end
  end
  Trainer-->>User: final metrics & checkpoints
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • sourcery-ai

Poem

I hop through logs and count each weight,
Dev batches trimmed to run quick and light,
Temp dirs vanish when no model is found,
Blacklists age and get cleaned from the ground,
Headers watch the UA — the pipeline's sound. 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch split/labels-training

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @uelkerd, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on minor but important code quality improvements within the emotion detection training pipeline. The primary changes involve refactoring logging statements to use f-strings for enhanced readability and accuracy, and standardizing the way model parameters are counted by leveraging a shared utility function. These updates ensure clearer logging output and promote consistent practices across the codebase without altering the existing behavior of the training pipeline.

Highlights

  • Refactored logging statements to use f-strings: Multiple logging statements within the training_pipeline.py file have been updated to use f-strings. This improves the clarity and correctness of log messages by ensuring variables are properly interpolated.
  • Standardized model parameter counting: The method for counting trainable model parameters has been standardized. Instead of an internal model method, the code now utilizes the count_model_params utility function from src/utils, promoting code reuse and maintainability.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@deepsource-io

deepsource-io Bot commented Aug 23, 2025

Copy link
Copy Markdown
Contributor

Here's the code health analysis summary for commits 1d6fb4e..50354ed. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource Test coverage LogoTest coverage⚠️ Artifact not reportedTimed out: Artifact was never reportedView Check ↗
DeepSource Python LogoPython✅ Success
🎯 76 occurences resolved
View Check ↗
DeepSource Terraform LogoTerraform✅ SuccessView Check ↗
DeepSource Secrets LogoSecrets✅ SuccessView Check ↗
DeepSource Shell LogoShell✅ SuccessView Check ↗
DeepSource Docker LogoDocker✅ SuccessView Check ↗

💡 If you’re a repository administrator, you can configure the quality gates from the settings.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request makes good improvements by correcting f-string formatting in many log messages and switching to a utility function for counting model parameters. This not only fixes broken logs but also makes the parameter count log more accurate by specifying trainable parameters.

While many f-strings were fixed, I noticed there are still several in src/models/emotion_detection/training_pipeline.py that use the old formatting. It would be great to fix these as well for consistency when you get a chance. Overall, a solid cleanup!

Comment thread src/models/emotion_detection/training_pipeline.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/models/emotion_detection/training_pipeline.py (3)

401-403: Complete the f-string migration; several logs still render literal braces (and one filename).

A number of log lines (and the non-best checkpoint filename) still use brace placeholders without f-strings, so values won’t interpolate and the checkpoint filename will literally contain “{epoch}”. Apply the patch below.

@@
-                logger.info(
-                    "Epoch {epoch}, Batch {batch_idx + 1}/{num_batches}, "
-                    "Loss: {avg_loss:.8f}, LR: {current_lr:.2e}"
-                )
+                logger.info(
+                    f"Epoch {epoch}, Batch {batch_idx + 1}/{num_batches}, "
+                    f"Loss: {avg_loss:.8f}, LR: {current_lr:.2e}"
+                )
@@
-                if avg_loss < 1e-8:
-                    logger.error("❌ CRITICAL: Average loss is suspiciously small: {avg_loss:.8f}")
-                if avg_loss > 100:
-                    logger.error("❌ CRITICAL: Average loss is suspiciously large: {avg_loss:.8f}")
+                if avg_loss < 1e-8:
+                    logger.error(f"❌ CRITICAL: Average loss is suspiciously small: {avg_loss:.8f}")
+                if avg_loss > 100:
+                    logger.error(f"❌ CRITICAL: Average loss is suspiciously large: {avg_loss:.8f}")
@@
-            if (batch_idx + 1) % val_frequency == 0:
-                logger.info("🔍 Validating at batch {batch_idx + 1}...")
+            if (batch_idx + 1) % val_frequency == 0:
+                logger.info(f"🔍 Validating at batch {batch_idx + 1}...")
                 self.validate(epoch)
@@
-                if self.should_stop_early():
-                    logger.info("🛑 Early stopping triggered at batch {batch_idx + 1}")
+                if self.should_stop_early():
+                    logger.info(f"🛑 Early stopping triggered at batch {batch_idx + 1}")
                     return {
@@
-        logger.info("Epoch {epoch} completed - Loss: {avg_loss:.4f}, Time: {epoch_time:.1f}s")
+        logger.info(f"Epoch {epoch} completed - Loss: {avg_loss:.4f}, Time: {epoch_time:.1f}s")
@@
-        logger.info("Validating model at epoch {epoch}...")
+        logger.info(f"Validating model at epoch {epoch}...")
@@
-                self.save_checkpoint(epoch, val_metrics, is_best=True)
-                logger.info("New best model saved! Macro F1: {current_score:.4f}")
+                self.save_checkpoint(epoch, val_metrics, is_best=True)
+                logger.info(f"New best model saved! Macro F1: {current_score:.4f}")
@@
-            logger.info(
-                "No improvement. Patience: {self.patience_counter}/{self.early_stopping_patience}"
-            )
+            logger.info(
+                f"No improvement. Patience: {self.patience_counter}/{self.early_stopping_patience}"
+            )
@@
-        else:
-            checkpoint_path = self.output_dir / "checkpoint_epoch_{epoch}.pt"
+        else:
+            checkpoint_path = self.output_dir / f"checkpoint_epoch_{epoch}.pt"
@@
-        torch.save(checkpoint, checkpoint_path)
-        logger.info("Checkpoint saved: {checkpoint_path}")
+        torch.save(checkpoint, checkpoint_path)
+        logger.info(f"Checkpoint saved: {checkpoint_path}")
@@
-            with Path(history_path).open("w") as f:
-                json.dump(serializable_history, f, indent=2)
-            logger.info("Training history saved to {history_path}")
+            with Path(history_path).open("w") as f:
+                json.dump(serializable_history, f, indent=2)
+            logger.info(f"Training history saved to {history_path}")
@@
-            with Path(history_path).open("w") as f:
-                json.dump(simplified_history, f, indent=2)
-            logger.info("Simplified training history saved to {history_path}")
+            with Path(history_path).open("w") as f:
+                json.dump(simplified_history, f, indent=2)
+            logger.info(f"Simplified training history saved to {history_path}")

Also applies to: 406-408, 411-416, 434-434, 447-447, 462-467, 501-505, 559-559, 579-579


259-264: Bug: hasattr(self, "model") always true here; use self.model is None.

__init__ sets self.model = None, so hasattr is always true and the model won’t be initialized if load_model is called on a fresh instance, causing an AttributeError when loading state dict.

-        if not hasattr(self, "model"):
+        if self.model is None:
             datasets = self.prepare_data()
             class_weights = datasets.get("class_weights")
             self.initialize_model(class_weights)

106-112: Avoid double dataset preparation (dev_mode gets silently overridden).

train_emotion_detection_model calls trainer.prepare_data(dev_mode=...) and then trainer.train(), which unconditionally calls self.prepare_data() again with default args. This both wastes time and overrides dev-mode sampling. Persist class_weights and only prepare if dataloaders aren’t set.

@@
         self.tokenizer = None
+        self.class_weights = None
@@
-        datasets = self.data_loader.prepare_datasets()
+        datasets = self.data_loader.prepare_datasets()
+        # Persist class weights for downstream initialization if prepare_data() was called externally
+        self.class_weights = datasets.get("class_weights")
@@
-        datasets = self.prepare_data()
-
-        class_weights = datasets.get("class_weights")
-        self.initialize_model(class_weights)
+        class_weights = None
+        if self.train_dataloader is None:
+            datasets = self.prepare_data()
+            class_weights = datasets.get("class_weights")
+        else:
+            class_weights = self.class_weights
+        self.initialize_model(class_weights)

If you prefer to keep train() unaware, alternatively remove the explicit trainer.prepare_data(...) call from train_emotion_detection_model and pass a dev_mode arg into train(). I can draft that variant if you want.

Also applies to: 134-136, 514-518

🧹 Nitpick comments (2)
src/models/emotion_detection/training_pipeline.py (2)

291-293: Validation cadence likely too sparse on small datasets.

val_frequency = max(500, num_batches // 5) means no mid-epoch validation unless you have ≥500 batches. Consider a floor of 1–5% of the epoch with an upper bound.

-        val_frequency = max(500, num_batches // 5)
+        # Validate roughly 5 times per epoch, with a sensible floor and cap
+        val_frequency = max(1, min(500, max(1, num_batches // 5)))
+        # Optional: make this configurable

299-365: Gate heavy first-batch diagnostics behind DEBUG level.

These logs compute reductions on GPU tensors and run every epoch’s first batch. They’re excellent during debugging but add overhead in production. Recommend logging them at DEBUG and flipping the logger level based on a debug_mode flag.

I can wire the debug_mode argument (present in train_emotion_detection_model) through to the trainer and switch these logger.info calls to logger.debug while setting logger.setLevel(logging.DEBUG) if enabled.

Also applies to: 368-390, 400-416

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1d6fb4e and 2fb1234.

📒 Files selected for processing (1)
  • src/models/emotion_detection/training_pipeline.py (12 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (python)
🔇 Additional comments (1)
src/models/emotion_detection/training_pipeline.py (1)

34-34: Good switch to shared param-count utility.

Importing and using count_model_params(..., only_trainable=True) centralizes logic and prevents drift vs. per-model implementations.

Also applies to: 245-246

Comment thread src/models/emotion_detection/training_pipeline.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/models/emotion_detection/training_pipeline.py (2)

258-266: Bug: hasattr check won’t initialize model when self.model is None

self.model is defined in init, so hasattr(self, "model") is always True. If load_model is called before initialize_model, this will skip initialization and self.model.load_state_dict will error. Check for None instead.

Apply:

-        if not hasattr(self, "model"):
+        if self.model is None:
             datasets = self.prepare_data()
             class_weights = datasets.get("class_weights")
             self.initialize_model(class_weights)

605-616: Public API change introduces behavioral drift: dev_mode defaults to True and unused debug_mode parameter

  • Defaulting dev_mode to True alters the previous behavior (now trains on 5% by default). This contradicts the PR claim that behavior is unchanged and can surprise downstream callers.
  • debug_mode is unused throughout the pipeline.

Revert dev_mode default to False and remove debug_mode (or wire it end-to-end in a separate PR).

Apply:

 def train_emotion_detection_model(
@@
-    device: Optional[str] = None,
-    dev_mode: bool = True,  # Enable development mode by default
-    debug_mode: bool = True,  # Enable debugging by default
+    device: Optional[str] = None,
+    dev_mode: bool = False,  # Development mode opt-in to preserve prior behavior
 ) -> Dict[str, Any]:

And update the docstring below accordingly (remove debug_mode).

♻️ Duplicate comments (1)
src/models/emotion_detection/training_pipeline.py (1)

34-34: Import path consistency and existence of src.utils

This absolute import deviates from the surrounding relative imports and may fail depending on packaging/runtime (src-layout vs installed package). Confirm the module exists and consider aligning import style for consistency.

Run to verify location and signature:

#!/bin/bash
set -euo pipefail

echo "Searching for count_model_params definition:"
rg -nP 'def\s+count_model_params\s*\(' src || true

echo "Listing src/utils locations:"
fd -t f utils src | sed -n '1,200p'

echo "Grepping for count_model_params usage across repo:"
rg -nF 'count_model_params(' || true
🧹 Nitpick comments (5)
src/models/emotion_detection/training_pipeline.py (5)

102-102: PR summary vs implementation: f-strings vs parameterized logging

The PR description says “convert logger calls to f-strings”, but the code uses parameterized logging (logger.info("...", args)), which is generally preferred. Either update the PR description to reflect parameterized logging or switch calls to f-strings to match the stated objective. Don’t mix styles.


213-222: Downgrade “DEBUG” diagnostics to debug level and/or gate behind a flag

These are verbose diagnostics but logged at INFO. Consider using logger.debug and a guard (e.g., if logger.isEnabledFor(logging.DEBUG):) to reduce noise in normal runs.

Apply this minimal pattern:

-        logger.info("🔍 DEBUG: Loss Function Analysis")
-        logger.info("   Loss function type: %s", type(self.loss_fn).__name__)
+        if logger.isEnabledFor(logging.DEBUG):
+            logger.debug("Loss Function Analysis")
+            logger.debug("   Loss function type: %s", type(self.loss_fn).__name__)

If you want a flag, introduce self.debug_mode in init and gate on it as well.


303-367: Verbose training diagnostics at INFO; consider demoting to DEBUG and gating

Large blocks of per-batch shape/stats logs at INFO will flood logs. Recommend logger.debug with an isEnabledFor(DEBUG) guard and/or a debug_mode flag to explicitly enable them.

Example pattern:

-            if batch_idx == 0:
-                logger.info("🔍 DEBUG: Data Distribution Analysis")
-                logger.info("   Labels shape: %s", labels.shape)
+            if batch_idx == 0 and logger.isEnabledFor(logging.DEBUG):
+                logger.debug("Data Distribution Analysis")
+                logger.debug("   Labels shape: %s", labels.shape)

Repeat for “Model Output Analysis”, “Loss Analysis”, and “Gradient Analysis” sections.


618-628: Docstring still advertises debug_mode and new default behavior

Update docstring to remove debug_mode and reflect dev_mode now being opt-in.

Apply:

-        device: Device to use for training (auto-detect if None)
-        dev_mode: Enable development mode with smaller dataset
-        debug_mode: Enable debugging mode with enhanced logging
+        device: Device to use for training (auto-detect if None)
+        dev_mode: If True, use a small subset of data for quicker iterations

649-651: Consider exposing dev_mode in CLI/main or environment instead of defaulting in API

train_emotion_detection_model is a convenient API; let callers choose dev_mode explicitly. For CLI, pass via flag or env to avoid accidental partial-data training.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cd2cb89 and 0e9d99f.

📒 Files selected for processing (1)
  • src/models/emotion_detection/training_pipeline.py (21 hunks)
🔇 Additional comments (3)
src/models/emotion_detection/training_pipeline.py (3)

392-393: Nice fix: robust formatting of clip_grad_norm_ return type

Casting to float avoids TypeError when clip_grad_norm_ returns a Tensor. Good defensive logging.


632-637: Logging copy reflects new defaults; adjust messages after reverting dev_mode default

If dev_mode becomes opt-in again, these messages are still fine. If you keep dev_mode default True, this section will fire by default and materially change behavior. Align with the chosen default.


246-251: Param count logging: verify utility existence and formatting approach

Please confirm that the count_model_params helper in src/utils is defined, accepts only_trainable=True, and returns an integer as expected. Additionally, since logger.info supports lazy interpolation, you may choose to pass the raw integer and let the logger handle formatting, or pre-format for readability.

• Confirm that count_model_params is implemented in src/utils and its signature includes only_trainable
• Ensure it returns an integer when only_trainable=True
• (Optional) Instead of format(..., ",d"), consider:

  • Passing the raw count to logger.info("…%d…", count) for lazy formatting
  • Precomputing a formatted string once, if that improves clarity

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/models/emotion_detection/training_pipeline.py (2)

263-267: Fix: checkpoint load never initializes model due to hasattr check

self.model is set to None in init, so hasattr(self, "model") is always True and the init branch is skipped, causing None.load_state_dict(...) to crash.

Apply:

-        if not hasattr(self, "model"):
+        if self.model is None:
             datasets = self.prepare_data()
             class_weights = datasets.get("class_weights")
             self.initialize_model(class_weights)

596-602: Ensure returned model_path exists even when no “best” checkpoint was saved

If no improvement occurs, best_model.pt may never be written; returning a non-existent path can break consumers.

-        results = {
+        best_model_path = self.output_dir / "best_model.pt"
+        if not best_model_path.exists():
+            # Fallback to the latest epoch checkpoint if best was never saved
+            ckpts = sorted(self.output_dir.glob("checkpoint_epoch_*.pt"))
+            if ckpts:
+                best_model_path = ckpts[-1]
+
+        results = {
             "final_test_metrics": test_metrics,
             "best_validation_score": self.best_score,
             "training_history": self.training_history,
-            "model_path": str(self.output_dir / "best_model.pt"),
+            "model_path": str(best_model_path),
             "total_epochs": len(self.training_history),
         }
♻️ Duplicate comments (1)
src/models/emotion_detection/training_pipeline.py (1)

34-34: Import path may be brittle; align with local style and verify utility exists

Absolute from src.utils ... can break outside a src-layout and is inconsistent with nearby relative imports. Prefer a relative import (adjust the level to match your layout) or ensure src is on PYTHONPATH. Also verify the utility actually exists to avoid runtime ImportError.

Apply one of the following (adjust dots as needed):

-from src.utils import count_model_params
+from ..utils import count_model_params  # adjust relative level to your package layout

To verify presence and path quickly:

#!/bin/bash
# Verify utility exists and import path works
set -euo pipefail
echo "Definitions of count_model_params:"
rg -nP '\bdef\s+count_model_params\s*\(' -C1 src || true
python - <<'PY'
try:
    from src.utils import count_model_params
    print("OK: src.utils.count_model_params import works")
except Exception as e:
    print("WARN: src.utils import failed:", e)
PY
🧹 Nitpick comments (3)
src/models/emotion_detection/training_pipeline.py (3)

296-297: Validation frequency can skip validation on small datasets

With val_frequency = max(500, num_batches // 5), small num_batches yields 500 and no mid-epoch validation. Consider clamping to at least 1 and at most 500.

-        val_frequency = max(500, num_batches // 5)
+        # Validate roughly 5x/epoch, but never less than once and never too chatty
+        val_frequency = max(1, min(500, max(1, num_batches // 5)))

374-385: Avoid .data in gradient norm calculation

Use .detach() (or directly p.grad.norm(2)) to avoid autograd footguns; .data is discouraged.

-                        param_norm = p.grad.data.norm(2)
+                        param_norm = p.grad.detach().norm(2)

639-645: Use debug_mode to dial logging verbosity

Right now, INFO-level diagnostics are very chatty even when debug_mode=False. Consider setting the logger level from this flag.

-    if dev_mode:
+    # Control verbosity early
+    logger.setLevel(logging.INFO if debug_mode else logging.WARNING)
+    if dev_mode:
         logger.info("🚀 DEVELOPMENT MODE ENABLED: Fast training with reduced dataset")
         logger.info("🚀 Expected training time: 30-60 minutes instead of 9 hours")
     else:
         logger.info("🏭 PRODUCTION MODE: Full dataset training")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0e9d99f and 882fcdb.

📒 Files selected for processing (1)
  • src/models/emotion_detection/training_pipeline.py (20 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (python)
🔇 Additional comments (11)
src/models/emotion_detection/training_pipeline.py (11)

102-102: Good switch to parameterized logging for device message

Lazy formatting via logger args avoids unnecessary string interpolation. LGTM.


161-166: Dev-mode batch-size bump is reasonable and well logged

Clear, bounded increase with informative logging. LGTM.


194-196: Dataset size logging converted correctly

Interpolations are now reliable; no stray braces. LGTM.


214-223: Loss/class-weight diagnostics look solid

Casts to scalars with .item() avoid Tensor-format errors. LGTM.


247-252: Param-count logging via utility: OK, minor nit

Using format(..., ",d") yields a readable, grouped number and is safe with %s. No action needed.


319-325: Per-class positives logging is correct and efficient

Limited to first 10 classes and scalarized counts. LGTM.


395-400: Nice robustness on clip_grad_norm_ return type

Casting to float avoids formatting errors when a Tensor is returned. LGTM.


445-448: Epoch summary logging fixed

Interpolations are now reliable and readable. LGTM.


461-482: Validation and patience logging improvements look good

Clear epoch context and patience tracking with safe formatting. LGTM.


516-520: Checkpoint naming and save log are fine

Explicit file name and path logging is clear. LGTM.


521-545: Order of operations in train() is sound

Data prep → initialize → train/validate loop with early stopping. LGTM.

Comment thread src/models/emotion_detection/training_pipeline.py Outdated
cursoragent and others added 2 commits August 23, 2025 21:59
Co-authored-by: denizcan.uelker <denizcan.uelker@mercedes-benz.com>
Resolved issues in src/models/emotion_detection/training_pipeline.py with DeepSource Autofix

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/models/emotion_detection/training_pipeline.py (2)

265-269: Bug: hasattr(self, "model") is always True; model may still be None

self.model is initialized to None in __init__, so hasattr(self, "model") won’t detect uninitialized state. Loading will crash on self.model.load_state_dict(...).

-        if not hasattr(self, "model"):
+        if self.model is None:
             datasets = self.prepare_data()
             class_weights = datasets.get("class_weights")
             self.initialize_model(class_weights)

553-559: Functional bug: prepare_data() is called twice and dev_mode is lost/overridden

train_emotion_detection_model calls trainer.prepare_data(dev_mode=dev_mode) and then trainer.train() calls self.prepare_data() again with the default dev_mode=False. This forces a full dataset reload and can unintentionally switch from dev to prod mid-run, potentially causing OOM due to the dev-mode batch size increase.

Option A (cleanest): thread dev_mode into train() and call prepare_data() only there. Remove the pre-call.

-    def train(self) -> Dict[str, Any]:
+    def train(self, dev_mode: bool = False) -> Dict[str, Any]:
...
-        datasets = self.prepare_data()
+        datasets = self.prepare_data(dev_mode=dev_mode)
-    trainer.prepare_data(dev_mode=dev_mode)
-
-    return trainer.train()
+    return trainer.train(dev_mode=dev_mode)

Option B (minimal): guard in train() to skip re-prep if already prepared.

-        datasets = self.prepare_data()
+        datasets = self.prepare_data() if self.train_dataloader is None else {"class_weights": None}

Recommend Option A.

Also applies to: 680-682

♻️ Duplicate comments (2)
src/models/emotion_detection/training_pipeline.py (2)

34-34: Import style + module availability: prefer relative import or provide fallback; verify count_model_params exists

This absolute import breaks consistency with nearby relative imports and may fail outside a src-layout. Provide a fallback relative import, or standardize on one style. Also verify the utility actually exists in the repo/package.

Apply this safer import pattern:

-from src.utils import count_model_params
+try:
+    # src-layout (editable install) support
+    from src.utils import count_model_params
+except ModuleNotFoundError:
+    # package-relative fallback for installed distributions
+    from ..utils import count_model_params

Run to verify presence/signature (no imports executed):

#!/bin/bash
rg -nP 'def\s+count_model_params\s*\(' -n src || { echo "Missing count_model_params utility"; exit 1; }
rg -nP '\bcount_model_params\(' -n src/models/emotion_detection/training_pipeline.py

373-377: Fix: boolean comparison on tensors is ambiguous

Using labels.sum() == 0 yields a Tensor; if on a Tensor raises “boolean value of Tensor is ambiguous”.

-        if labels.sum() == 0:
+        total_positives = int(labels.sum().item())
+        if total_positives == 0:
             logger.error("❌ CRITICAL: All labels are zero!")
-        elif labels.sum() == labels.numel():
+        elif total_positives == labels.numel():
             logger.error("❌ CRITICAL: All labels are one!")
🧹 Nitpick comments (6)
src/models/emotion_detection/training_pipeline.py (6)

241-247: Clamp warmup steps to avoid scheduler misconfiguration when total_steps is small

If total_steps < warmup_steps the scheduler can be ill‑configured (and occasionally error depending on HF version). Clamp and log the adjustment.

-        total_steps = len(self.train_dataloader) * self.num_epochs
+        total_steps = len(self.train_dataloader) * self.num_epochs
+        # Guard: avoid warmup > total steps
+        effective_warmup = min(self.warmup_steps, max(0, total_steps - 1))
...
-        self.scheduler = get_linear_schedule_with_warmup(
+        self.scheduler = get_linear_schedule_with_warmup(
             self.optimizer,
-            num_warmup_steps=self.warmup_steps,
+            num_warmup_steps=effective_warmup,
             num_training_steps=total_steps,
         )
+        if effective_warmup != self.warmup_steps:
+            logger.info(
+                "Adjusted warmup steps from %d to %d to fit total steps=%d",
+                self.warmup_steps, effective_warmup, total_steps
+            )

249-254: Param-count logging works; minor nit on pre-formatting

Works as-is. If you want to keep lazy logging benefits, pre-format once and pass as an arg rather than formatting within the call.

-        logger.info(
-            "Model initialized with %s trainable parameters",
-            format(count_model_params(self.model, only_trainable=True), ",d"),
-        )
+        param_count = count_model_params(self.model, only_trainable=True)
+        logger.info("Model initialized with %s trainable parameters", f"{param_count:,}")

421-433: Avoid .data; use .detach() for grad norm computation

Accessing .data is discouraged and can lead to subtle autograd issues. Use .grad.detach().

-        for p in self.model.parameters():
-            if p.grad is not None:
-                param_norm = p.grad.data.norm(2)
+        for p in self.model.parameters():
+            if p.grad is not None:
+                param_norm = p.grad.detach().norm(2)
                 total_norm += param_norm.item() ** 2
                 param_count += 1

439-442: Casting clip norm: prefer .item() for tensors

float(tensor) typically works for 0-D tensors, but .item() is explicit and avoids surprises across devices.

-        clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm
+        clip_val = clip_norm.item() if isinstance(clip_norm, torch.Tensor) else clip_norm
         logger.info("   Gradient norm after clipping: %.6f", clip_val)

560-571: Skip duplicate validation when early stop triggers mid-epoch

When _maybe_validate_and_early_stop returns early, train() still performs per-epoch validation immediately after, duplicating work. Short-circuit if early_stopped is present.

         for epoch in range(1, self.num_epochs + 1):
             train_metrics = self.train_epoch(epoch)

             if self.evaluation_strategy == "epoch":
+                if train_metrics.get("early_stopped"):
+                    self.training_history.append(train_metrics)
+                    logger.info("Early stop: skipping end-of-epoch validation at epoch %d", epoch)
+                    break
                 val_metrics = self.validate(epoch)

102-102: Logging style is consistent but diverges from the PR objective

You’ve standardized on parameterized logging ("%s"/"%.4f") rather than f-strings. That’s fine (and preferred for lazy formatting), but it conflicts with the PR description claim “convert to f-strings”. Consider updating the PR text to avoid confusion.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 882fcdb and 420b472.

📒 Files selected for processing (1)
  • src/models/emotion_detection/training_pipeline.py (17 hunks)
🔇 Additional comments (4)
src/models/emotion_detection/training_pipeline.py (4)

646-647: Breaking API/behavior: default dev_mode switched to False; PR text claims “behavior unchanged”

Changing dev_mode default from True→False (and logging production-mode by default) alters runtime behavior and expected training time. Either revert the default, or update PR description, docs, and any callers.

  • Confirm previous default and intended behavior.
  • If keeping the new default, add a short note in the docstring and README/CHANGELOG.

Also applies to: 664-668


214-225: Loss/class-weight debug logging looks solid

Nice use of logger.isEnabledFor(logging.DEBUG) gating and .item() extraction for scalars.


354-357: Epoch summary logging: solid and concise

Clear, numeric formatting and consistent units.


542-546: Checkpoint naming/logging LGTM

The f-string path and post-save info log are clear and consistent.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/models/emotion_detection/hf_loader.py (1)

201-210: Fix path traversal risk in archive extraction (tar/zip).

Using tarfile.extractall and ZipFile.extractall on untrusted archives allows path traversal (e.g., files writing outside extract_dir). This is a high-severity security issue given the code downloads from arbitrary URLs.

Apply this diff to replace unsafe extraction with a safe extractor:

-            if archive_path.endswith(".tar.gz") or archive_path.endswith(".tgz"):
-                with tarfile.open(archive_path, "r:gz") as tar:
-                    tar.extractall(path=extract_dir)
+            if archive_path.endswith(".tar.gz") or archive_path.endswith(".tgz"):
+                with tarfile.open(archive_path, "r:gz") as tar:
+                    _safe_extract_tar(tar, extract_dir)
             elif archive_path.endswith(".zip"):
-                import zipfile
-
-                with zipfile.ZipFile(archive_path, "r") as zf:
-                    zf.extractall(path=extract_dir)
+                import zipfile
+                with zipfile.ZipFile(archive_path, "r") as zf:
+                    _safe_extract_zip(zf, extract_dir)

Add these helpers (outside the shown range, e.g., near module top):

import logging
logger = logging.getLogger(__name__)

def _is_within(base: str, target: str) -> bool:
    base_abs = os.path.abspath(base)
    target_abs = os.path.abspath(target)
    return os.path.commonprefix([base_abs + os.sep, target_abs + os.sep]) == base_abs + os.sep

def _safe_extract_tar(tar: tarfile.TarFile, path: str) -> None:
    for member in tar.getmembers():
        dest = os.path.join(path, member.name)
        if not _is_within(path, dest):
            raise ValueError(f"Unsafe tar member path: {member.name}")
    tar.extractall(path=path)

def _safe_extract_zip(zf, path: str) -> None:
    for name in zf.namelist():
        dest = os.path.join(path, name)
        if not _is_within(path, dest):
            raise ValueError(f"Unsafe zip member path: {name}")
    zf.extractall(path=path)

Optionally, log and clean up on errors to avoid leaving partial extracts behind. Would you like me to wire in robust cleanup/logging as well?

Also applies to: 202-209, 209-209

src/models/emotion_detection/training_pipeline.py (1)

566-582: Fix metric key names: use f1_macro and f1_micro.

evaluate_emotion_classifier returns keys f1_macro and f1_micro. Accessing macro_f1/micro_f1 will KeyError.

Apply this diff:

-        current_score = val_metrics["macro_f1"]
+        current_score = val_metrics["f1_macro"]
@@
-                logger.info("New best model saved! Macro F1: %.4f", current_score)
+                logger.info("New best model saved! Macro F1: %.4f", current_score)
@@
-        logger.info("Best validation Macro F1: %.4f", self.best_score)
-        logger.info("Final test Macro F1: %.4f", test_metrics["macro_f1"])
-        logger.info("Final test Micro F1: %.4f", test_metrics["micro_f1"])
+        logger.info("Best validation Macro F1: %.4f", self.best_score)
+        logger.info("Final test Macro F1: %.4f", test_metrics["f1_macro"])
+        logger.info("Final test Micro F1: %.4f", test_metrics["f1_micro"])

Also applies to: 712-716

♻️ Duplicate comments (2)
src/models/emotion_detection/training_pipeline.py (2)

22-22: Import style consistency: consider relative import or keep consistent with rest of file.

This absolute import differs from the relative imports used below. Stick to one style for maintainability. If src.utils is a top-level util by design, ignore this.

Would you prefer me to switch to a relative import (e.g., from ...utils import count_model_params) if utils lives under src, or keep absolute across the file?


378-406: Fix ambiguous Tensor booleans in label checks.

Using labels.sum() directly in if statements raises “boolean value of Tensor is ambiguous”.

Apply:

-        if labels.sum() == 0:
+        if labels.sum().item() == 0:
             logger.error("❌ CRITICAL: All labels are zero!")
-        elif labels.sum() == labels.numel():
+        elif labels.sum().item() == labels.numel():
             logger.error("❌ CRITICAL: All labels are one!")
🧹 Nitpick comments (13)
src/models/emotion_detection/hf_loader.py (4)

186-194: Replace blanket try/except-pass with debug logging to aid diagnostics.

Several branches swallow exceptions silently. At minimum, log at debug with the source that failed so operators can troubleshoot without enabling full tracing.

Proposed minimal changes:

-        except Exception:
-            pass
+        except Exception as e:
+            logger.debug("Local dir load failed for %s: %s", local_dir, e)

Repeat similarly for the HF Hub direct/snapshot and archive branches. Add import logging and logger = logging.getLogger(__name__) at module top if not present.

Also applies to: 214-229


176-183: Pathlib and platform-appropriate cache dirs.

  • Prefer pathlib.Path over os.path.* for readability and to satisfy PTH1xx hints.
  • Defaults like "/var/tmp/hf-cache" can trip security auditors. Consider using Hugging Face defaults or platform caches (e.g., appdirs.user_cache_dir).

Example:

-from os.path import join, dirname, basename, exists, isdir
-from pathlib import Path
+from pathlib import Path

-cache_base = os.getenv("HF_HOME", "/var/tmp/hf-cache")
-snap_dir = snapshot_download(repo_id=model_id, token=token, cache_dir=cache_base)
+cache_base = Path(os.getenv("HF_HOME", str(Path.home() / ".cache" / "huggingface"))))
+snap_dir = snapshot_download(repo_id=model_id, token=token, cache_dir=str(cache_base))

Also applies to: 189-194, 215-217, 218-221


66-77: threshold argument is unused in remote inference.

The predict signature exposes threshold, but the value isn’t applied. Either:

  • Use it to filter or choose labels (align with HFEmotionDetector), or
  • Drop it from the signature for consistency.

Would you like me to wire threshold-based top-K/threshold filtering to match local model behavior?

Also applies to: 84-99


214-221: Candidate discovery could be more selective.

Scanning extract_dir plus all first-level subdirs is pragmatic, but consider limiting to directories containing both config.json and either pytorch_model.bin or model.safetensors to cut false positives.

I can propose a small helper that validates a candidate dir before attempting _wrap_local_model.

src/security/jwt_manager.py (3)

49-51: Comment is stale: Token pair is not a plain dict.

The code returns a TokenResponse pydantic model, not a plain dict. Update the comment to avoid confusion.

-# Token pair is returned as a plain dict
+# Token pair is returned as a TokenResponse model

69-71: Use timezone-aware UTC and consistent numeric timestamps.

Naive datetimes (utcnow/fromtimestamp) trigger linter warnings and can cause subtle bugs. Prefer timezone-aware UTC and store numeric seconds in JWTs for consistency.

-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
...
-            "exp": datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
-            "iat": datetime.utcnow(),
+            "exp": datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
+            "iat": datetime.now(timezone.utc),
...
-            "exp": datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS),
-            "iat": datetime.utcnow(),
+            "exp": datetime.now(timezone.utc) + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS),
+            "iat": datetime.now(timezone.utc),
...
-            exp_datetime = (
-                datetime.fromtimestamp(exp_timestamp) if exp_timestamp else None
-            )
+            exp_datetime = (
+                datetime.fromtimestamp(exp_timestamp, tz=timezone.utc) if exp_timestamp else None
+            )

Alternatively, encode exp/iat as ints via .timestamp() to avoid tz conversion later.

Also applies to: 81-85, 133-136


105-113: Avoid logging token material; prefer jti or a hash.

Even partial token logs can be sensitive. Consider logging a hash or a JWT ID (jti) claim instead.

-            logger.warning(f"Token expired: {token[:10]}...")
+            logger.warning("Token expired: %s", hashlib.sha256(token.encode()).hexdigest()[:12])

Or add a jti claim at creation and log that.

src/security_headers.py (5)

2-5: Docstring nits: Punctuation and spacing.

Minor style fixes per D205/D415: end the summary with punctuation and leave a blank line before the description.

-"""🛡️ Security Headers Middleware
-==============================
-Flask middleware for adding security headers and implementing security policies.
-"""
+"""🛡️ Security Headers Middleware.
+
+==============================
+Flask middleware for adding security headers and implementing security policies.
+"""

68-75: Prefer pathlib and configurable config path.

Pathlib improves readability, and a configurable security.yaml path (e.g., via Flask config or env) aids deployment across environments.

-            config_path = os.path.join(
-                os.path.dirname(__file__), "../configs/security.yaml"
-            )
-            with open(config_path) as f:
+            from pathlib import Path
+            base_dir = Path(__file__).resolve().parent
+            config_path = Path(os.getenv("SECURITY_CONFIG", base_dir / "../configs/security.yaml")).resolve()
+            with config_path.open() as f:
                 security_config = yaml.safe_load(f)

92-99: PII/log volume caution for security logs.

Logging remote_addr, X-Forwarded-For, and raw UA may be considered PII. Consider:

  • Masking IPs (e.g., /24 aggregation) or hashing.
  • Sampling logs in high-traffic paths.
  • Making individual fields opt-in via SecurityHeadersConfig.

I can add config flags like redact_ips/redact_user_agent and a small helper to redact before logging—want me to draft it?

Also applies to: 220-236, 463-483


246-317: Deduplicate and precompile UA pattern lists.

  • "checker" appears twice in low_risk_patterns.
  • Consider converting lists to tuples or sets and deduplicating to avoid double counting.
  • You can also precompute lowercase sets once for a small perf win.
-        low_risk_patterns = [
+        low_risk_patterns = [
             "indexer",
             "feed",
             "rss",
             "aggregator",
             "monitor",
-            "checker",
+            "checker",
             "validator",
             "linter",
-            "checker",
             "analyzer",
         ]

If you’d like, I can send a follow-up diff to use sets and avoid repeated scoring.

Also applies to: 318-331


24-44: Config surface duplication is confusing (enable_csp vs enable_content_security_policy, enable_hsts vs enable_strict_transport_security).

You expose both names but only use the latter ones. Either:

  • Drop the unused duplicates, or
  • Wire them as synonyms to reduce confusion.

Happy to prep a cleanup commit mapping legacy names to the new ones to avoid breaking external code.

Also applies to: 487-516

src/models/emotion_detection/training_pipeline.py (1)

479-491: Minor: simplify clip_norm cast.

Use a one-liner conditional cast (SIM108). No behavioral change.

-        if not isinstance(clip_norm, (int, float)):
-            clip_val = float(clip_norm)
-        else:
-            clip_val = clip_norm
+        clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 420b472 and f6adc1c.

📒 Files selected for processing (4)
  • src/models/emotion_detection/hf_loader.py (7 hunks)
  • src/models/emotion_detection/training_pipeline.py (20 hunks)
  • src/security/jwt_manager.py (9 hunks)
  • src/security_headers.py (12 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/models/emotion_detection/training_pipeline.py (2)
src/models/emotion_detection/bert_classifier.py (2)
  • create_bert_emotion_classifier (385-411)
  • evaluate_emotion_classifier (414-469)
src/models/emotion_detection/dataset_loader.py (2)
  • GoEmotionsDataset (33-79)
  • create_goemotions_loader (321-333)
src/security/jwt_manager.py (2)
src/unified_ai_api.py (1)
  • refresh_token (1029-1061)
tests/unit/test_jwt_manager_extra.py (1)
  • utcnow (56-58)
🪛 Ruff (0.12.2)
src/models/emotion_detection/hf_loader.py

111-111: Use X | None for type annotations

Convert to X | None

(UP045)


112-112: Use X | None for type annotations

Convert to X | None

(UP045)


131-131: Use X | None for type annotations

Convert to X | None

(UP045)


131-131: Use X | None for type annotations

Convert to X | None

(UP045)


161-162: try-except-pass detected, consider logging the exception

(S110)


170-171: try-except-pass detected, consider logging the exception

(S110)


176-176: Probable insecure usage of temporary file or directory: "/var/tmp/hf-cache"

(S108)


183-184: try-except-pass detected, consider logging the exception

(S110)


189-189: Probable insecure usage of temporary file or directory: "/var/tmp/hf-cache"

(S108)


190-190: os.path.join() should be replaced by Path with / operator

(PTH118)


191-191: os.makedirs() should be replaced by Path.mkdir(parents=True)

(PTH103)


192-192: os.path.basename() should be replaced by Path.name

(PTH119)


193-193: os.path.join() should be replaced by Path with / operator

(PTH118)


209-209: Uses of tarfile.extractall()

(S202)


215-215: os.path.join() should be replaced by Path with / operator

(PTH118)


215-215: Use pathlib.Path.iterdir() instead.

(PTH208)


218-218: os.path.isdir() should be replaced by Path.is_dir()

(PTH112)


218-218: os.path.exists() should be replaced by Path.exists()

(PTH110)


219-219: os.path.join() should be replaced by Path with / operator

(PTH118)

src/security_headers.py

2-5: 1 blank line required between summary line and description

(D205)


2-5: First line should end with a period, question mark, or exclamation point

Add closing punctuation

(D415)


68-68: os.path.join() should be replaced by Path with / operator

(PTH118)


246-246: Too many branches (16 > 12)

(PLR0912)

src/models/emotion_detection/training_pipeline.py

486-489: Use ternary operator clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm instead of if-else-block

Replace if-else-block with clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm

(SIM108)


542-542: Undefined name val_frequency

(F821)


551-551: Undefined name start_time

(F821)

src/security/jwt_manager.py

69-69: datetime.datetime.utcnow() used

(DTZ003)


70-70: datetime.datetime.utcnow() used

(DTZ003)


81-81: datetime.datetime.utcnow() used

(DTZ003)


82-82: datetime.datetime.utcnow() used

(DTZ003)


135-135: datetime.datetime.fromtimestamp() called without a tz argument

(DTZ006)

🔇 Additional comments (4)
src/security/jwt_manager.py (1)

156-170: LGTM: Efficient expired-token cleanup loop.

Separation of collection and deletion avoids dict mutation during iteration; returns removed count. Solid.

src/security_headers.py (1)

118-126: Headers look correct; nice comprehensive coverage.

Good explicit control over all major security headers and sane defaults.

Also applies to: 129-164

src/models/emotion_detection/training_pipeline.py (2)

263-268: Nice: switch to count_model_params utility and formatted logging.

This produces clearer parameter counts and centralizes counting logic.


144-168: Dev-mode dataset scaling/logging reads well.

The 5%/10% sampling and batch scaling with clear logs are helpful for quick iterations.

Comment thread src/models/emotion_detection/training_pipeline.py Outdated
Comment thread src/models/emotion_detection/training_pipeline.py Outdated
Comment thread src/security_headers.py
Resolved issues in src/models/emotion_detection/training_pipeline.py with DeepSource Autofix

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/models/emotion_detection/training_pipeline.py (2)

700-706: Avoid double dataset prep (dev_mode gets overwritten); thread dev_mode through train.

train_emotion_detection_model prepares data with dev_mode, then train() prepares again without it, effectively discarding dev-mode sampling. Unify the flow by passing dev_mode into train() and removing the extra call.

-def train(self) -> Dict[str, Any]:
+def train(self, dev_mode: bool = False) -> Dict[str, Any]:
@@
-        Returns:
-            Dictionary with training results and final metrics
+        Returns:
+            Dictionary with training results and final metrics
@@
-        datasets = self.prepare_data()
+        datasets = self.prepare_data(dev_mode=dev_mode)

And in the convenience function:

-    trainer.prepare_data(dev_mode=dev_mode)
-
-    return trainer.train()
+    return trainer.train(dev_mode=dev_mode)

Also applies to: 708-712, 833-835


273-284: Fix checkpoint load path: self.model exists but is None; hasattr won’t initialize.

__init__ sets self.model = None, so hasattr(self, "model") is True. You need to check for None.

-        if not hasattr(self, "model"):
+        if self.model is None:
             datasets = self.prepare_data()
             class_weights = datasets.get("class_weights")
             self.initialize_model(class_weights)
♻️ Duplicate comments (4)
src/models/emotion_detection/training_pipeline.py (4)

645-653: Fix metric keys: use f1_macro from evaluator.

evaluate_emotion_classifier returns f1_macro/f1_micro. Accessing macro_f1 will raise KeyError and break early-stopping/best-model logic.

-        current_score = val_metrics["macro_f1"]
+        current_score = val_metrics["f1_macro"]
@@
-                logger.info("New best model saved! Macro F1: %.4f", current_score)
+                logger.info("New best model saved! Macro F1: %.4f", current_score)

314-323: Fix NameError and misnamed arg: pass val_frequency and start_time + rename avg_losstotal_loss.

_maybe_validate_and_early_stop references val_frequency and start_time that are not in scope, and you’re passing total_loss into a parameter named avg_loss. This will throw at runtime and also miscomputes train loss.

Apply at call site:

-            maybe_metrics = self._maybe_validate_and_early_stop(
-                batch_idx, epoch, num_batches, total_loss, self.scheduler.get_last_lr()[0]
-            )
+            maybe_metrics = self._maybe_validate_and_early_stop(
+                batch_idx=batch_idx,
+                epoch=epoch,
+                num_batches=num_batches,
+                total_loss=total_loss,
+                current_lr=self.scheduler.get_last_lr()[0],
+                val_frequency=val_frequency,
+                start_time=start_time,
+            )

593-627: Finalize _maybe_validate_and_early_stop API to match call site and fix docstring.

Accept val_frequency and start_time, and use total_loss to compute average correctly. Also fix the Returns docstring (it returns Optional[Dict], not bool).

-    def _maybe_validate_and_early_stop(
-        self,
-        batch_idx: int,
-        epoch: int,
-        num_batches: int,
-        avg_loss: float,
-        current_lr: float,
-    ) -> Optional[Dict[str, Any]]:
+    def _maybe_validate_and_early_stop(
+        self,
+        batch_idx: int,
+        epoch: int,
+        num_batches: int,
+        total_loss: float,
+        current_lr: float,
+        val_frequency: int,
+        start_time: float,
+    ) -> Optional[Dict[str, Any]]:
@@
-        Returns:
-            bool: True if training should stop early, False otherwise
+        Returns:
+            Optional[Dict[str, Any]]: Metrics snapshot if early-stopped, else None
@@
-        if (batch_idx + 1) % val_frequency != 0:
+        if (batch_idx + 1) % val_frequency != 0:
             return None
@@
-            return {
+            return {
                 "epoch": epoch,
-                "train_loss": avg_loss / (batch_idx + 1),
-                "epoch_time": time.time() - start_time,
+                "train_loss": total_loss / (batch_idx + 1),
+                "epoch_time": time.time() - start_time,
                 "learning_rate": current_lr,
                 "early_stopped": True,
             }

456-470: Fix ambiguous Tensor boolean in label sanity checks.

if labels.sum() == 0: and == labels.numel() yield tensor booleans; branching on them raises “boolean value of Tensor is ambiguous”.

-        if labels.sum() == 0:
+        pos_sum = int(labels.sum().item())
+        if pos_sum == 0:
             logger.error("❌ CRITICAL: All labels are zero!")
-        elif labels.sum() == labels.numel():
+        elif pos_sum == labels.numel():
             logger.error("❌ CRITICAL: All labels are one!")
🧹 Nitpick comments (3)
src/models/emotion_detection/training_pipeline.py (3)

253-260: Guard against warmup > total_steps.

If len(train_dataloader) * num_epochswarmup_steps, the scheduler will spend the entire training warming up. Clamp warmup to total_steps - 1.

-        self.scheduler = get_linear_schedule_with_warmup(
-            self.optimizer,
-            num_warmup_steps=self.warmup_steps,
-            num_training_steps=total_steps,
-        )
+        self.scheduler = get_linear_schedule_with_warmup(
+            self.optimizer,
+            num_warmup_steps=min(self.warmup_steps, max(0, total_steps - 1)),
+            num_training_steps=total_steps,
+        )

557-561: One-liner for casting clip_norm (SIM108).

Minor style: use a ternary.

-        if not isinstance(clip_norm, (int, float)):
-            clip_val = float(clip_norm)
-        else:
-            clip_val = clip_norm
+        clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm

219-235: Debug gating is good; consider logger.debug within debug-only helpers.

You guard calls with logger.isEnabledFor(DEBUG) but inner logs use info. Switching to debug keeps log levels semantically aligned; no functional change.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f6adc1c and 2e1682f.

📒 Files selected for processing (1)
  • src/models/emotion_detection/training_pipeline.py (20 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/models/emotion_detection/training_pipeline.py (2)
src/models/emotion_detection/bert_classifier.py (3)
  • create_bert_emotion_classifier (385-411)
  • evaluate_emotion_classifier (414-469)
  • unfreeze_bert_layers (128-146)
src/models/emotion_detection/dataset_loader.py (1)
  • GoEmotionsDataset (33-79)
🪛 Ruff (0.12.2)
src/models/emotion_detection/training_pipeline.py

557-560: Use ternary operator clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm instead of if-else-block

Replace if-else-block with clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm

(SIM108)


613-613: Undefined name val_frequency

(F821)


622-622: Undefined name start_time

(F821)

🔇 Additional comments (1)
src/models/emotion_detection/training_pipeline.py (1)

261-265: I’ve requested a scan for any existing parameter‐count helper under src/utils. Once we verify whether count_model_params (or a similarly named function) exists—and its import path—we can confirm if the inline sum indeed needs replacement, or if the original comment about using a helper is incorrect.

[next steps]

  • If the helper exists, refactor to use it and update imports accordingly.
  • If no helper exists, decide whether to implement count_model_params in src/utils (or remove the suggestion).

Comment thread src/models/emotion_detection/training_pipeline.py
Comment thread src/models/emotion_detection/training_pipeline.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/models/emotion_detection/training_pipeline.py (3)

278-285: load_model: check for None, not attribute existence

self.model is defined in __init__ and may be None. hasattr(self, "model") will be True, leading to None.load_state_dict(...) and an exception. Check for None instead. Also, avoid re‑preparing data unless needed.

-        if not hasattr(self, "model"):
-            datasets = self.prepare_data()
-            class_weights = datasets.get("class_weights")
-            self.initialize_model(class_weights)
+        if self.model is None:
+            datasets = self.prepare_data()
+            self.initialize_model(datasets.get("class_weights"))

698-701: Avoid double dataset preparation and dev_mode override

train_emotion_detection_model already calls prepare_data(dev_mode=...). Calling self.prepare_data() here redoes work and silently overrides dev-mode settings. Prefer a single entry point.

Minimal, cohesive fix: let train() drive preparation and accept dev_mode, and remove the prepare step from train_emotion_detection_model.

  1. Update train() to accept dev_mode and prepare data:
-    def train(self) -> Dict[str, Any]:
+    def train(self, dev_mode: bool = False) -> Dict[str, Any]:
@@
-        datasets = self.prepare_data()
+        datasets = self.prepare_data(dev_mode=dev_mode)
         class_weights = datasets.get("class_weights")
         self.initialize_model(class_weights)
  1. Remove the extra prepare call and forward dev_mode:
-    trainer.prepare_data(dev_mode=dev_mode)
-
-    return trainer.train()
+    return trainer.train(dev_mode=dev_mode)

775-776: Fix final test-metrics keys to match evaluator output

The evaluate_emotion_classifier function returns its F1 scores under the keys f1_micro and f1_macro. In training_pipeline.py, the code is currently accessing non-existent keys macro_f1 and micro_f1, which will raise a KeyError.

Locations needing update:

  • src/models/emotion_detection/training_pipeline.py lines 775–776

Suggested diff:

-        logger.info(f"Final test Macro F1: {test_metrics['macro_f1']:.4f}")
-        logger.info(f"Final test Micro F1: {test_metrics['micro_f1']:.4f}")
+        logger.info(f"Final test Macro F1: {test_metrics['f1_macro']:.4f}")
+        logger.info(f"Final test Micro F1: {test_metrics['f1_micro']:.4f}")
♻️ Duplicate comments (4)
src/models/emotion_detection/training_pipeline.py (4)

22-25: Import style consistency: prefer one style (relative vs absolute) within this module

This file uses relative imports for local modules (Lines 24–25) but an absolute import for src.utils (Line 22). Pick one style for maintainability; relative would be from ...utils import count_model_params given the package layout.

-from src.utils import count_model_params
+from ...utils import count_model_params

314-317: Undefined names downstream: pass val_frequency and start_time; also pass the correct loss accumulator

_maybe_validate_and_early_stop references val_frequency and start_time but they’re not in scope inside that method. Also, you pass total_loss but name the parameter avg_loss inside the callee. Pass both missing values and align the parameter name.

-            maybe_metrics = self._maybe_validate_and_early_stop(
-                batch_idx, epoch, num_batches, total_loss, self.scheduler.get_last_lr()[0]
-            )
+            maybe_metrics = self._maybe_validate_and_early_stop(
+                batch_idx=batch_idx,
+                epoch=epoch,
+                num_batches=num_batches,
+                total_loss=total_loss,
+                current_lr=self.scheduler.get_last_lr()[0],
+                val_frequency=val_frequency,
+                start_time=start_time,
+            )

463-467: Fix ambiguous Tensor boolean comparisons

labels.sum() == 0 and labels.sum() == labels.numel() yield Tensor booleans, which are invalid in if. Convert to Python scalars.

-        if labels.sum() == 0:
+        total_positives = int(labels.sum().item())
+        if total_positives == 0:
             logger.error("❌ CRITICAL: All labels are zero!")
-        elif labels.sum() == labels.numel():
+        elif total_positives == labels.numel():
             logger.error("❌ CRITICAL: All labels are one!")

585-618: Fix _maybe_validate_and_early_stop: undefined names, wrong param naming, and incorrect return docstring

  • Uses val_frequency and start_time without defining them.
  • Parameter avg_loss is actually the running total_loss.
  • Docstring “Returns: bool …” contradicts actual return type (Optional[Dict[str, Any]]).

Refactor signature and internal usage accordingly.

-    def _maybe_validate_and_early_stop(
-        self,
-        batch_idx: int,
-        epoch: int,
-        num_batches: int,
-        avg_loss: float,
-        current_lr: float,
-    ) -> Optional[Dict[str, Any]]:
-        """Check if validation should be performed and handle early stopping.
+    def _maybe_validate_and_early_stop(
+        self,
+        batch_idx: int,
+        epoch: int,
+        num_batches: int,
+        total_loss: float,
+        current_lr: float,
+        val_frequency: int,
+        start_time: float,
+    ) -> Optional[Dict[str, Any]]:
+        """Check if validation should be performed and handle early stopping.
@@
-        Returns:
-            bool: True if training should stop early, False otherwise
+        Returns:
+            Optional[Dict[str, Any]]: Metrics snapshot if early-stopped; otherwise None.
@@
-        if (batch_idx + 1) % val_frequency != 0:
+        if (batch_idx + 1) % val_frequency != 0:
             return None
         logger.info(f"🔍 Validating at batch {batch_idx + 1}...")
         self.validate(epoch)
         if self.should_stop_early():
             logger.info(f"🛑 Early stopping triggered at batch {batch_idx + 1}")
             return {
                 "epoch": epoch,
-                "train_loss": avg_loss / (batch_idx + 1),
+                "train_loss": total_loss / (batch_idx + 1),
                 "epoch_time": time.time() - start_time,
                 "learning_rate": current_lr,
                 "early_stopped": True,
             }
         return None
🧹 Nitpick comments (6)
src/utils.py (1)

5-5: Remove unused import to satisfy Ruff F401

from typing import Union is unused in this module. Drop it to keep the file clean and pass linting.

-from typing import Union
src/models/emotion_detection/training_pipeline.py (5)

371-379: Log “before clipping” truly before clipping

You compute clip_grad_norm_ (in‑place) and then log “before” stats. Swap order so diagnostics are accurate.

-        # Gradient clipping
-        clip_norm = torch.nn.utils.clip_grad_norm_(
-            self.model.parameters(), max_norm=1.0
-        )
-
-        # Log gradient stats for first batch
-        if batch_idx == 0:
-            self._log_gradient_stats_before()
-            self._log_gradient_stats_after(clip_norm)
+        # Log gradient stats for first batch (before clipping)
+        if batch_idx == 0:
+            self._log_gradient_stats_before()
+
+        # Gradient clipping
+        clip_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)
+
+        # Log gradient stats for first batch (after clipping)
+        if batch_idx == 0:
+            self._log_gradient_stats_after(clip_norm)

535-541: Avoid .data usage on grads; use .detach() or tensor directly

Accessing p.grad.data is discouraged. Use p.grad (it’s already a Tensor) or p.grad.detach() when computing norms.

-            if p.grad is not None:
-                param_norm = p.grad.data.norm(2)
+            if p.grad is not None:
+                param_norm = p.grad.detach().norm(2)
                 total_norm += param_norm.item() ** 2

554-559: Micro cleanup: simplify clip_val assignment

Use a conditional expression as suggested by Ruff SIM108.

-        if not isinstance(clip_norm, (int, float)):
-            clip_val = float(clip_norm)
-        else:
-            clip_val = clip_norm
+        clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm

446-462: Level mismatch: debug-gated blocks log at INFO

These helpers early-return unless DEBUG is enabled, yet use logger.info for their messages. Consider switching to logger.debug to match intent (applies in _log_data_distribution, _log_model_output, _log_loss_analysis).

Example:

-        logger.info("🔍 DEBUG: Data Distribution Analysis")
+        logger.debug("🔍 DEBUG: Data Distribution Analysis")

384-391: Unify periodic progress logging via _log_progress

You already have _log_progress, but here you inline a different format. Consider delegating to _log_progress for consistency and easier tuning.

-            logger.info(
-                "Epoch %d, Batch %d/%d, Loss: %.8f",
-                epoch, batch_idx + 1, num_batches, loss.item()
-            )
+            self._log_progress(epoch, batch_idx, num_batches, total_loss=total_loss + 0.0)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2e1682f and df67ceb.

📒 Files selected for processing (2)
  • src/models/emotion_detection/training_pipeline.py (18 hunks)
  • src/utils.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/models/emotion_detection/training_pipeline.py (3)
src/utils.py (1)
  • count_model_params (8-21)
src/models/emotion_detection/bert_classifier.py (3)
  • create_bert_emotion_classifier (385-411)
  • evaluate_emotion_classifier (414-469)
  • unfreeze_bert_layers (128-146)
src/models/emotion_detection/dataset_loader.py (2)
  • GoEmotionsDataset (33-79)
  • create_goemotions_loader (321-333)
🪛 Ruff (0.12.2)
src/utils.py

5-5: typing.Union imported but unused

Remove unused import: typing.Union

(F401)

src/models/emotion_detection/training_pipeline.py

554-557: Use ternary operator clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm instead of if-else-block

Replace if-else-block with clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm

(SIM108)


605-605: Undefined name val_frequency

(F821)


614-614: Undefined name start_time

(F821)

🔇 Additional comments (4)
src/utils.py (1)

8-21: Utility looks correct and side‑effect free

Implementation returns exact parameter counts and respects only_trainable. Clear docstring. No changes needed.

src/models/emotion_detection/training_pipeline.py (3)

263-267: Nice improvement: centralized parameter-count logging

Using count_model_params(..., only_trainable=True) with thousands separator via f-string is clear and robust.


629-649: Validation flow is clear and logs are actionable

Good use of metric gating and best‑model checkpointing. No required changes.


773-777: Overall logging improvements deliver the PR objective

Converting curly-brace literals to f-strings and using the shared util for param counts significantly improves log clarity.

Resolved issues in src/utils.py with DeepSource Autofix

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/utils.py (2)

5-5: Remove unused import to satisfy linter (F401).

typing.Union is imported but not used.

-from typing import Union

1-1: Shebang is unnecessary for a library module.

This file isn’t intended to be executed directly; consider removing the shebang to avoid implying executability.

-#!/usr/bin/env python3
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between df67ceb and 854b2e7.

📒 Files selected for processing (1)
  • src/utils.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.12.2)
src/utils.py

5-5: typing.Union imported but unused

Remove unused import: typing.Union

(F401)

🔇 Additional comments (2)
src/utils.py (2)

8-20: Utility implementation LGTM.

Straightforward and correct parameter counting; optional trainable-only mode is clear and efficient.


8-20: All count_model_params calls already use only_trainable=True
I verified across the codebase that the only external invocation of count_model_params (in src/models/emotion_detection/training_pipeline.py at line 264) passes only_trainable=True. There are no single-argument calls (which would default to False) nor any calls explicitly setting only_trainable=False. No changes are needed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/models/emotion_detection/training_pipeline.py (1)

666-674: Fix metric keys: use f1_macro/f1_micro to match evaluator output

evaluate_emotion_classifier returns "f1_macro" and "f1_micro", not "macro_f1"/"micro_f1". Current code will KeyError at runtime in both validate() and final summary logging.

-        current_score = val_metrics["macro_f1"]
+        current_score = val_metrics["f1_macro"]
@@
-        logger.info("Final test Macro F1: %.4f", test_metrics["macro_f1"])
-        logger.info("Final test Micro F1: %.4f", test_metrics["micro_f1"])
+        logger.info("Final test Macro F1: %.4f", test_metrics["f1_macro"])
+        logger.info("Final test Micro F1: %.4f", test_metrics["f1_micro"])

Cross-check: src/models/emotion_detection/bert_classifier.py returns f1_micro/f1_macro. Ensure any consumers elsewhere also use the same keys.

Also applies to: 805-807

♻️ Duplicate comments (4)
src/models/emotion_detection/training_pipeline.py (4)

22-22: Import style: prefer relative to stay consistent with local imports

Other imports in this module use relative paths; switch this one as well for consistency across the file.

-from src.utils import count_model_params
+from ...utils import count_model_params

381-390: Log “before clipping” stats before calling clip_grad_norm_

clip_grad_norm_ updates gradients in-place. Currently “before” is logged after clipping, which is misleading. Swap the order.

-        # Gradient clipping
-        clip_norm = torch.nn.utils.clip_grad_norm_(
-            self.model.parameters(), max_norm=1.0
-        )
-
-        # Log gradient stats for first batch
-        if batch_idx == 0:
-            self._log_gradient_stats_before()
-            self._log_gradient_stats_after(clip_norm)
+        # Log gradient stats for first batch (before clipping)
+        if batch_idx == 0:
+            self._log_gradient_stats_before()
+
+        # Gradient clipping
+        clip_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)
+
+        # Log gradient stats for first batch (after clipping)
+        if batch_idx == 0:
+            self._log_gradient_stats_after(clip_norm)

483-487: Fix ambiguous Tensor boolean checks in logging helper

labels.sum() == 0 yields a Tensor; using it in if raises “boolean value of Tensor is ambiguous”. Convert to Python scalars.

-        if labels.sum() == 0:
+        if labels.sum().item() == 0:
             logger.error("❌ CRITICAL: All labels are zero!")
-        elif labels.sum() == labels.numel():
+        elif labels.sum().item() == labels.numel():
             logger.error("❌ CRITICAL: All labels are one!")

315-327: Correct loss variable semantics and make call explicit

You pass total_loss but the callee parameter is named avg_loss and then divided again, producing a double-division error in the returned metrics when early stopping triggers. Rename the parameter to total_loss and compute the average inside; also use keyword args to prevent future ordering bugs.

-            maybe_metrics = self._maybe_validate_and_early_stop(
-                batch_idx,
-                epoch,
-                num_batches,
-                total_loss,
-                self.scheduler.get_last_lr()[0],
-                val_frequency,
-                start_time,
-            )
+            maybe_metrics = self._maybe_validate_and_early_stop(
+                batch_idx=batch_idx,
+                epoch=epoch,
+                num_batches=num_batches,
+                total_loss=total_loss,
+                current_lr=self.scheduler.get_last_lr()[0],
+                val_frequency=val_frequency,
+                start_time=start_time,
+            )
@@
-    def _maybe_validate_and_early_stop(
+    def _maybe_validate_and_early_stop(
         self,
         batch_idx: int,
         epoch: int,
         num_batches: int,
-        avg_loss: float,
+        total_loss: float,
         current_lr: float,
         val_frequency: int,
         start_time: float,
     ) -> Optional[Dict[str, Any]]:
@@
-            avg_loss: Average loss for current epoch
+            total_loss: Accumulated loss so far in the current epoch
@@
-            return {
+            return {
                 "epoch": epoch,
-                "train_loss": avg_loss / (batch_idx + 1),
+                "train_loss": total_loss / (batch_idx + 1),
                 "epoch_time": time.time() - start_time,
                 "learning_rate": current_lr,
                 "early_stopped": True,
             }

Also applies to: 610-648

🧹 Nitpick comments (3)
src/models/emotion_detection/training_pipeline.py (3)

574-578: Nit: simplify cast per Ruff SIM108

Inline the cast using a ternary for brevity; behavior unchanged.

-        if not isinstance(clip_norm, (int, float)):
-            clip_val = float(clip_norm)
-        else:
-            clip_val = clip_norm
+        clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm

307-309: Validation frequency guard: avoid “never validate within epoch” on small datasets

max(500, num_batches // 5) means for typical dev runs with <500 batches you will never hit intra-epoch validation. If you intend step-based validation, consider max(1, num_batches // 5). If you only validate per-epoch (default), consider skipping this block entirely when evaluation_strategy == "epoch".

-        val_frequency = max(500, num_batches // 5)
+        val_frequency = max(1, num_batches // 5)

473-482: Use debug level inside debug-only helper

This helper runs only when DEBUG is enabled; emitting INFO-level lines can add noise if the gate changes later. Prefer logger.debug here for consistency.

-        logger.info("🔍 DEBUG: Data Distribution Analysis")
-        logger.info("   Labels shape: %s", labels.shape)
-        logger.info("   Labels dtype: %s", labels.dtype)
-        logger.info("   Labels min: %s", labels.min().item())
-        logger.info("   Labels max: %s", labels.max().item())
-        logger.info("   Labels mean: %.6f", labels.float().mean().item())
-        logger.info("   Labels sum: %s", labels.sum().item())
-        logger.info("   Non-zero labels: %s", (labels > 0).sum().item())
-        logger.info("   Total labels: %s", labels.numel())
+        logger.debug("🔍 DEBUG: Data Distribution Analysis")
+        logger.debug("   Labels shape: %s", labels.shape)
+        logger.debug("   Labels dtype: %s", labels.dtype)
+        logger.debug("   Labels min: %s", labels.min().item())
+        logger.debug("   Labels max: %s", labels.max().item())
+        logger.debug("   Labels mean: %.6f", labels.float().mean().item())
+        logger.debug("   Labels sum: %s", labels.sum().item())
+        logger.debug("   Non-zero labels: %s", (labels > 0).sum().item())
+        logger.debug("   Total labels: %s", labels.numel())
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 854b2e7 and 83a35c8.

📒 Files selected for processing (1)
  • src/models/emotion_detection/training_pipeline.py (20 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/models/emotion_detection/training_pipeline.py (3)
src/utils.py (1)
  • count_model_params (8-20)
src/models/emotion_detection/bert_classifier.py (3)
  • create_bert_emotion_classifier (385-411)
  • evaluate_emotion_classifier (414-469)
  • unfreeze_bert_layers (128-146)
src/models/emotion_detection/dataset_loader.py (1)
  • GoEmotionsDataset (33-79)
🪛 Ruff (0.12.2)
src/models/emotion_detection/training_pipeline.py

574-577: Use ternary operator clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm instead of if-else-block

Replace if-else-block with clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm

(SIM108)

🔇 Additional comments (1)
src/models/emotion_detection/training_pipeline.py (1)

263-266: Good switch to shared util for parameter counting

Using the central utility improves consistency and testability. Nice touch formatting the count with thousands separators.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/models/emotion_detection/training_pipeline.py (1)

658-680: Use correct evaluator keys and ensure checkpoints save when save_best_only=False

evaluate_emotion_classifier returns f1_macro/f1_micro (see bert_classifier.evaluate_emotion_classifier). Accessing macro_f1 will KeyError. Also, when save_best_only is False, no checkpoints are saved at all.

-        current_score = val_metrics["macro_f1"]
+        current_score = val_metrics["f1_macro"]
@@
-            if self.save_best_only:
-                self.save_checkpoint(epoch, val_metrics, is_best=True)
+            if self.save_best_only:
+                self.save_checkpoint(epoch, val_metrics, is_best=True)
+            else:
+                # Save per-epoch checkpoint even if not "best only"
+                self.save_checkpoint(epoch, val_metrics, is_best=False)

Optional: Consider always saving per-epoch checkpoints and, independently, updating best_model.pt when a new best is found.

♻️ Duplicate comments (4)
src/models/emotion_detection/training_pipeline.py (4)

22-25: Prefer consistent relative import for in-repo modules

To align with the relative imports used elsewhere in this file (e.g., Lines 24–25), import the utility via a relative path. This avoids mixing import styles and eases refactors if the top-level package name changes.

-from src.utils import count_model_params
+from ...utils import count_model_params

If the package layout lacks the necessary init.py files, keep the absolute import; otherwise, prefer the relative one for consistency.


381-390: “Before clipping” stats are logged after clipping — swap order

clip_grad_norm_ mutates gradients in-place. Log pre-clip stats before calling it, then log the returned post-clip norm.

-        # Gradient clipping
-        clip_norm = torch.nn.utils.clip_grad_norm_(
-            self.model.parameters(), max_norm=1.0
-        )
-
-        # Log gradient stats for first batch
-        if batch_idx == 0:
-            self._log_gradient_stats_before()
-            self._log_gradient_stats_after(clip_norm)
+        # Log gradient stats for first batch (before clipping)
+        if batch_idx == 0:
+            self._log_gradient_stats_before()
+
+        # Gradient clipping
+        clip_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)
+
+        # Log gradient stats for first batch (after clipping)
+        if batch_idx == 0:
+            self._log_gradient_stats_after(clip_norm)

483-487: Fix ambiguous tensor booleans in label-distribution checks

labels.sum() == 0 and labels.sum() == labels.numel() produce tensor booleans; use .item() to branch with Python booleans.

-        if labels.sum() == 0:
+        sum_pos = int(labels.sum().item())
+        if sum_pos == 0:
             logger.error("❌ CRITICAL: All labels are zero!")
-        elif labels.sum() == labels.numel():
+        elif sum_pos == labels.numel():
             logger.error("❌ CRITICAL: All labels are one!")

804-808: Final-summary metric keys must match evaluator output

Switch to f1_macro and f1_micro to avoid KeyError at runtime.

-        logger.info("Final test Macro F1: %.4f", test_metrics["macro_f1"])
-        logger.info("Final test Micro F1: %.4f", test_metrics["micro_f1"])
+        logger.info("Final test Macro F1: %.4f", test_metrics["f1_macro"])
+        logger.info("Final test Micro F1: %.4f", test_metrics["f1_micro"])
🧹 Nitpick comments (3)
src/models/emotion_detection/training_pipeline.py (3)

545-566: Avoid .data in gradient norm analysis

Using .data bypasses autograd semantics; prefer .detach() for safe, read-only tensors.

-            if p.grad is not None:
-                param_norm = p.grad.data.norm(2)
+            if p.grad is not None:
+                param_norm = p.grad.detach().norm(2)
                 total_norm += param_norm.item() ** 2

574-579: Simplify clip-norm casting (Ruff SIM108)

Tighten the cast to a single expression.

-        if not isinstance(clip_norm, (int, float)):
-            clip_val = float(clip_norm)
-        else:
-            clip_val = clip_norm
+        clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm

4-6: Docstring overclaims features not present here

The module-level docstring mentions “focal loss, temperature scaling, and ensemble methods,” which aren’t implemented in this file. Consider trimming to avoid confusing readers.

-This module provides a comprehensive training pipeline for the BERT-based emotion
-detection model with advanced features like focal loss, temperature scaling, and
-ensemble methods.
+This module provides a training pipeline for a BERT-based emotion
+detection model with progressive unfreezing, detailed logging, and evaluation utilities.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 83a35c8 and 0ea88e9.

📒 Files selected for processing (1)
  • src/models/emotion_detection/training_pipeline.py (20 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/models/emotion_detection/training_pipeline.py (3)
src/utils.py (1)
  • count_model_params (8-20)
src/models/emotion_detection/bert_classifier.py (3)
  • create_bert_emotion_classifier (385-411)
  • evaluate_emotion_classifier (414-469)
  • unfreeze_bert_layers (128-146)
src/models/emotion_detection/dataset_loader.py (2)
  • GoEmotionsDataset (33-79)
  • create_goemotions_loader (321-333)
🪛 Ruff (0.12.2)
src/models/emotion_detection/training_pipeline.py

574-577: Use ternary operator clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm instead of if-else-block

Replace if-else-block with clip_val = float(clip_norm) if not isinstance(clip_norm, (int, float)) else clip_norm

(SIM108)

🔇 Additional comments (5)
src/models/emotion_detection/training_pipeline.py (5)

145-168: Dev-mode dataset and batch-size logging reads clearly

Nice clarity improvements: the dev-mode banner, reduced sampling, and explicit batch-size change log make runs easy to audit.


198-203: Dataset size summary is precise and cheap

Good use of %d placeholders; avoids f-string eager formatting and logs exact sizes.


221-236: Loss/weights debug logs are gated correctly

DEBUG gating avoids noisy logs in INFO runs; numeric summaries (min/mean/max) are helpful for diagnosing class-weight issues.


263-268: Parameter-count logging via utility is correct

Using count_model_params with only_trainable=True and formatting with thousands separators is spot-on.


300-302: Progressive unfreezing hook is clean and well-scoped

Calling _handle_progressive_unfreezing early in the epoch is appropriate; the helper logs context and delegates to model.unfreeze_bert_layers.

Also applies to: 331-341

Comment thread src/models/emotion_detection/training_pipeline.py Outdated
Comment thread src/models/emotion_detection/training_pipeline.py Outdated
@d-ulker
d-ulker merged commit cf674e2 into main Aug 24, 2025
11 of 14 checks passed
@d-ulker
d-ulker deleted the split/labels-training branch August 24, 2025 14:12
d-ulker added a commit that referenced this pull request Sep 7, 2025
chore(training): correct f-strings; log param counts via utils
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants